package org.langcom.io;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

public class FlatSerializationUtil {

	public static final String DELIM = "#";
	public static final String DATA_DELIM = "@";

	public static String serialize(Object obj) {
		List<Field> fieldList = new ArrayList<Field>();
		if (isPrimitive(obj.getClass())) {
			return obj.toString();
		}
		if (List.class.isAssignableFrom(obj.getClass())) {
			return null;
		}
		exploreFields(fieldList, obj.getClass());
		Iterator<Field> iterator = fieldList.iterator();
		StringBuilder builder = new StringBuilder();
		builder.append(obj.getClass().getName());
		Collections.sort(fieldList, new Comparator<Field>() {

			@Override
			public int compare(Field o1, Field o2) {
				return o1.getName().compareTo(o2.getName());
			}
		});
		builder.append(DATA_DELIM);
		while (iterator.hasNext()) {
			Field next = iterator.next();
			if (isPrimitive(next.getType())) {
				next.setAccessible(true);
				Object value;
				try {
					value = next.get(obj);
					if (value != null) {
						builder.append(value);
					}
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
			builder.append(DELIM);
		}
		return builder.toString();
	}

	// className#[field1...]@data;data2;...
	public static <T> T deserialize(String str) {
		T result = null;
		try {
			String[] split = str.split(DATA_DELIM);
			String classname = split[0];
			result = (T) Class.forName(classname).getConstructor().newInstance();
			List<Field> fieldList = new ArrayList<Field>();
			exploreFields(fieldList, result.getClass());
			Collections.sort(fieldList, new Comparator<Field>() {

				@Override
				public int compare(Field o1, Field o2) {
					return o1.getName().compareTo(o2.getName());
				}
			});
			String[] split3 = split[1].split(DELIM);
			int count = 0;
			for (Field field : fieldList) {
				if (count >= split3.length) {
					break;
				}
				if (!split3[count].isEmpty()) {
					getValue(result, split3, count, field);
				}
				count++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	public static <T> T deserialize(Class klass, String str) {
		T result = null;
		try {
			String[] split = str.split(DATA_DELIM);
			String classname = split[0];
			result = (T) klass.getConstructor().newInstance();
			if (split.length == 1) {
				return result;
			}
			List<Field> fieldList = new ArrayList<Field>();
			exploreFields(fieldList, result.getClass());
			Collections.sort(fieldList, new Comparator<Field>() {

				@Override
				public int compare(Field o1, Field o2) {
					return o1.getName().compareTo(o2.getName());
				}
			});
			String[] split3 = split[1].split(DELIM);
			int count = 0;
			for (Field field : fieldList) {
				if (count >= split3.length) {
					break;
				}
				if (split3[count].length() > 0) {
					getValue(result, split3, count, field);
				}
				count++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	private static <T> void getValue(T result, String[] split3, int count, Field field) {
		Class<? extends Object> klass = field.getType();
		try {
			field.setAccessible(true);
			if (klass.equals(String.class)) {
				field.set(result, split3[count]);
			} else if (klass.equals(Integer.class)) {
				field.set(result, Integer.valueOf(split3[count]));
			} else if (klass.equals(Long.class)) {
				field.set(result, Long.valueOf(split3[count]));
			} else if (klass.equals(Character.class)) {
				field.set(result, split3[count]);
			} else if (klass.equals(Boolean.class)) {
				field.set(result, Boolean.valueOf(split3[count]));
			} else if (klass.equals(BigDecimal.class)) {
				field.set(result, new BigDecimal(split3[count]));
			} else if (klass.equals(Date.class)) {
				field.set(result, new SimpleDateFormat().parse(split3[count]));
			} else if (klass.equals(Double.class)) {
				field.set(result, Double.valueOf(split3[count]));
			} else if (klass.equals(Float.class)) {
				field.set(result, Float.valueOf(split3[count]));
			} else if (klass.equals(BigInteger.class)) {
				field.set(result, new BigInteger(split3[count]));
			} else if (klass.equals(Byte.class)) {
				field.set(result, split3[count].getBytes());
			} else if (klass.equals(int.class)) {
				field.set(result, Integer.valueOf(split3[count]));
			} else if (klass.equals(double.class)) {
				field.set(result, Double.valueOf(split3[count]));
			} else if (klass.equals(float.class)) {
				field.set(result, Float.valueOf(split3[count]));
			} else if (klass.equals(boolean.class)) {
				field.set(result, Boolean.valueOf(split3[count]));
			} else if (klass.equals(long.class)) {
				field.set(result, Long.valueOf(split3[count]));
			} else if (klass.equals(char.class)) {
				field.set(result, split3[count]);
			} else if (klass.equals(byte.class)) {
				field.set(result, split3[count]);
			} else {
				// field.set(result, klass.getConstructor().newInstance());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static boolean isPrimitive(Class<?> klass) {
		if (klass != null) {
			if (klass.equals(String.class) || klass.equals(Integer.class) || klass.equals(Long.class) || klass.equals(Character.class) || klass.equals(Boolean.class) || klass.equals(Object.class)
				|| klass.equals(BigDecimal.class) || klass.equals(Date.class) || klass.equals(Double.class) || klass.equals(Float.class) || klass.equals(BigInteger.class) || klass.equals(Byte.class)
				|| klass.equals(int.class) || klass.equals(double.class) || klass.equals(float.class) || klass.equals(boolean.class) || klass.equals(long.class) || klass.equals(char.class)
				|| klass.equals(byte.class) || klass.equals(byte[].class) || klass.equals(char[].class) || klass.equals(void.class) || klass.isEnum()) {
				return true;
			}
		}
		return false;
	}

	public static void exploreFields(List<Field> fieldList, Class<?> clazz) {
		fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
		Iterator<Field> iterator = fieldList.iterator();
		while (iterator.hasNext()) {
			Field next = iterator.next();
			if (Modifier.isTransient(next.getModifiers()) || Modifier.isFinal(next.getModifiers())) {
				iterator.remove();
				continue;
			}
		}
		if (clazz.getSuperclass() != null) {
			exploreFields(fieldList, clazz.getSuperclass());
		}
	}
}